﻿/// <summary>
/// Developer: CHOOI KAH WAI
/// Last Modified: 26/10/2009
/// </summary>

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Media.Media3D;
using System.Collections;
using System.Drawing;
using System.Timers;
using System.IO;
using System.Windows.Markup;
using ARSS.Components;
using ARSS.Common;
using ARSS.Visual;
using ARSS.Client;
using ARSS.Util;
using System.Windows.Media.Animation;

namespace ARSS.Visual
{
	/// <summary>
	/// Interaction logic for PresentWindow.xaml
	/// </summary>
	public partial class PresentWindow : Window , IPresenter
    {

        #region Variables

        /// <summary>
        /// List of action markers in use
        /// </summary>
        private Dictionary<int, ActionMarker> actions;

        /// <summary>
        /// Root scene node
        /// </summary>
        private SceneNode rootSceneNode;

        // Animation usage

        /// <summary>
        /// Previous Time
        /// </summary>
        DateTime previousTime;

        /// <summary>
        /// Current animating key frame
        /// </summary>
        int currentAnimationFrame;

        /// <summary>
        /// Animation time elapse
        /// </summary>
        double animationTimeElapsed = 0;

        /// <summary>
        /// Current animation set
        /// </summary>
        ArrayList currentAnimationSet = null;
        //

        #endregion

        #region Functions

        /// <summary>
        /// Constructor
        /// </summary>
        public PresentWindow()
		{
			this.InitializeComponent();

			// Insert code required on object creation below this point.

            this.Loaded += new RoutedEventHandler(PresentWindow_Loaded);
            this.Closing += new System.ComponentModel.CancelEventHandler(PresentWindow_Closing);
            

            // Catch network data
            Core.Instance.MyData.DataReceived += new ClientData.DataReceivedHandler(OnDataReceived);
		}

        /// <summary>
        /// Process network data packet from server
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="data"></param>
        protected void ProcessNetworkPacket(ClientData sender, byte[] data)
        {
            // Process data
            MemoryStream buffer = new MemoryStream(data);

            byte[] intBuffer = new byte[4];
            buffer.Read(intBuffer, 0, 4);

            int actionCode = BitConverter.ToInt32(intBuffer, 0);
            switch (actionCode)
            {
                case (int)NetworkActionCodesEnum.SetSceneAnimation:
                    {
                        // Get animation name length
                        buffer.Read(intBuffer, 0, 4);
                        int length = BitConverter.ToInt32(intBuffer, 0);
                        byte[] animationBuffer = new byte[length];
                        // Get animation name data
                        buffer.Read(animationBuffer, 0, length);

                        // Start the animation
                        StartAnimation(System.Text.ASCIIEncoding.UTF8.GetString(animationBuffer));
                    }
                    break;
                case (int)NetworkActionCodesEnum.LockNode:
                    {
                        // Get node name length
                        buffer.Read(intBuffer, 0, 4);
                        int length = BitConverter.ToInt32(intBuffer, 0);
                        byte[] nodeName = new byte[length];
                        // Get node name data
                        buffer.Read(nodeName, 0, length);
                        // Get locker nickname length
                        buffer.Read(intBuffer, 0, 4);
                        length = BitConverter.ToInt32(intBuffer, 0);
                        byte[] nickName = new byte[length];
                        // Get locker nickname data
                        buffer.Read(nickName, 0, length);

                        // Append locker nickname to the node name
                        SceneNode lockedNode = FindSceneNode(System.Text.Encoding.UTF8.GetString(nodeName), Core.Instance.SceneData.Root);
                        lockedNode.Locker = System.Text.Encoding.UTF8.GetString(nickName);
                    }
                    break;
                case (int)NetworkActionCodesEnum.ReleaseNode:
                    {
                        // Get node name length
                        buffer.Read(intBuffer, 0, 4);
                        int length = BitConverter.ToInt32(intBuffer, 0);
                        byte[] nodeName = new byte[length];
                        // Get node name data
                        buffer.Read(nodeName, 0, length);

                        string nodeNameString = System.Text.Encoding.UTF8.GetString(nodeName);

                        // Is this node currently locked by me?
                        foreach (ActionMarker am in this.actions.Values)
                        {
                            if (am.LockedNode != null)
                            {
                                if (am.LockedNode.Name.CompareTo(nodeNameString) == 0)
                                {
                                    am.LockedNode.Locker = "";
                                    // Yes, release it
                                    ActionMarkerReleased(am);
                                    return;
                                }
                            }
                        }

                        // Not locked by me
                        SceneNode lockedNode = FindSceneNode(System.Text.Encoding.UTF8.GetString(nodeName), Core.Instance.SceneData.Root);
                        lockedNode.Locker = "";

                    }
                    break;
                case (int)NetworkActionCodesEnum.UpdateMatrix:
                    {
                        // Get node name length
                        buffer.Read(intBuffer, 0, 4);
                        int length = BitConverter.ToInt32(intBuffer, 0);
                        byte[] nodeName = new byte[length];
                        // Get node name data
                        buffer.Read(nodeName, 0, length);

                        // Read transformation matrix
                        double[] mat = new double[16];
                        byte[] singleMatrixBuffer = new byte[8];
                        for(int i = 0; i < 16; i++)
                        {
                            buffer.Read(singleMatrixBuffer, 0, 8);
                            mat[i] = BitConverter.ToDouble(singleMatrixBuffer, 0);
                        }

                        SceneNode lockedNode = FindSceneNode(System.Text.Encoding.UTF8.GetString(nodeName), Core.Instance.SceneData.Root);
                        lockedNode.TransformMatrix = Util.MatrixUtil.GetWPFMatrix(mat);
                        
                    }
                    break;
            }
        }

        /// <summary>
        /// Network data notification
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="data"></param>
        protected void OnDataReceived(ClientData sender, byte[] data)
        {
            // Process the network data in dispatcher thread only
            this.Dispatcher.Invoke((Action)delegate { ProcessNetworkPacket(sender, data); }, null);
        }

        /// <summary>
        /// Find scene node according to given name
        /// </summary>
        /// <param name="name"></param>
        /// <param name="sourceNode"></param>
        /// <returns></returns>
        protected SceneNode FindSceneNode(string name, SceneNode sourceNode)
        {
            SceneNode node = null;

            if (sourceNode.Name.CompareTo(name) != 0)
            {
                foreach (SceneNode child in sourceNode.Children)
                {
                    node = FindSceneNode(name, child);
                    if (node != null) break;
                }
            }
            else
            {
                node = sourceNode;
            }
            return node;
        }

        /// <summary>
        /// Load the scene node models
        /// </summary>
        /// <param name="node">Scene node</param>
        protected void PrepareSceneNode(SceneNode node)
        {
            FileStream fs = new FileStream(node.ModelPath, FileMode.Open);
            node.Model3D = (ModelVisual3D)XamlReader.Load(fs);
            fs.Close();

            ModelContainer.Children.Add(node.Model3D);

            // Load its children
            foreach (SceneNode child in node.Children)
            {
                PrepareSceneNode(child);
            }
        }

        /// <summary>
        /// Hide scene node
        /// </summary>
        /// <param name="_model">Model to hide</param>
        protected void HideModel(ModelVisual3D model)
        {
            model.Transform = new ScaleTransform3D(0, 0, 0);
        }

        /// <summary>
        /// Window loaded event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void PresentWindow_Loaded(object sender, RoutedEventArgs e)
        {
            // Prepare SceneNodes
            this.rootSceneNode = Core.Instance.SceneData.Root;
            PrepareSceneNode(this.rootSceneNode);

            // Prepare Actions
            this.actions = new Dictionary<int, ActionMarker>();
            ActionMarker am;
            foreach (SceneNode node in Core.Instance.SceneData.Actions)
            {
                am = new ActionMarker();
                FileStream fs = new FileStream(node.ModelPath, FileMode.Open);
                node.Model3D = (ModelVisual3D)XamlReader.Load(fs);
                HideModel(node.Model3D); // Hide the marker first

                am.ARCode = node.MarkerID;
                am.SNode = node;
  
                ModelContainer.Children.Add(node.Model3D);
                this.actions.Add(am.ARCode, am);
            }
           
            // Done preparations, start the presentation now
            Core.Instance.StartPresentation();

            Core.Instance.MyData.ClientDisconnected += new ClientData.ClientDisconnectedHandler(OnDisconnectedFromServer);
        }

        /// <summary>
        /// Client disconnected
        /// </summary>
        /// <param name="sender"></param>
        protected void OnDisconnectedFromServer(ClientData sender)
        {
            MessageBox.Show("Disconnected from server.", "Disconnected", MessageBoxButton.OK, MessageBoxImage.Error);

            // Go back to main window
            this.Dispatcher.Invoke((Action)delegate
            {
                this.Close();
            }, null);
        }

        /// <summary>
        /// Window closing event handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void PresentWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            Core.Instance.MyData.ClientDisconnected -= new ClientData.ClientDisconnectedHandler(OnDisconnectedFromServer);

            // Release memory
            ModelContainer = null;

            if(Core.Instance.MyData.TCPSocket.Connected)
                Core.Instance.MyData.Disconnect();

            // Go back to main window
            Core.Instance.CurrentState = Core.emSTATE.emINIT;
        }

        /// <summary>
        /// Update child's transformation matrix
        /// Final matrix = child_matrix x parent_matrix
        /// </summary>
        /// <param name="_parentNode">Parent node</param>
        /// <param name="_matrix">Paren't transformation matrix</param>
        protected void UpdateChildMatrix(SceneNode parentNode, Matrix3D matrix)
        {
            Matrix3D newMatrix;
            foreach (SceneNode child in parentNode.Children)
            {
                newMatrix = Matrix3D.Multiply(child.TransformMatrix, matrix);

                MatrixTransform3D mt = new MatrixTransform3D();
                mt.Matrix = newMatrix;
                child.Model3D.Transform = mt;

                // update its children
                UpdateChildMatrix(child, newMatrix);
            }
        }

        /// <summary>
        /// Action marker was released
        /// </summary>
        /// <param name="am">The marker</param>
        public void ActionMarkerReleased(ActionMarker am)
        {
            am.IsUsing = false;

            // Attach to the locked scene node back to root scene node
            // Substract root scene node's matrix transformation
            SceneNode lockedNode = am.LockedNode;
            am.SNode.Children.Remove(lockedNode);
            lockedNode.ParentNode = this.rootSceneNode;
            this.rootSceneNode.Children.Add(lockedNode);
            Matrix3D mm = lockedNode.ParentNode.Model3D.Transform.Value;
            mm.Invert();
            Matrix3D mTrans = Matrix3D.Multiply(lockedNode.Model3D.Transform.Value, mm);

            lockedNode.TransformMatrix = mTrans;

            // Hide the marker object
            HideModel(am.SNode.Model3D);

            am.release();

            Logger.Instance.Log(am.SNode.Name + " released.");
        }

        /// <summary>
        /// Synchronize the scene node matrix
        /// </summary>
        /// <param name="sceneNode"></param>
        protected void SyncrhonizeNodeMatrix(SceneNode sceneNode)
        {
            Matrix3D mm = Core.Instance.SceneData.Root.Model3D.Transform.Value;
            mm.Invert();
            Matrix3D mTrans = Matrix3D.Multiply(sceneNode.Model3D.Transform.Value, mm);
            double[] matrixToSend = Util.MatrixUtil.GetMatrixFromWPF(mTrans);

            // Send the transformation matrix to server
            MemoryStream ms = new MemoryStream();
            // Action code
            ms.Write(BitConverter.GetBytes((int)NetworkActionCodesEnum.UpdateMatrix), 0, 4);
            byte[] nodeNameBuffer = System.Text.ASCIIEncoding.UTF8.GetBytes(sceneNode.Name);
            // Node Name Length
            ms.Write(BitConverter.GetBytes(nodeNameBuffer.Length), 0, 4);
            // Node Name buffer
            ms.Write(nodeNameBuffer, 0, nodeNameBuffer.Length);
            // Write actual matrix
            for (int i = 0; i < 16; i++)
            {
                ms.Write(BitConverter.GetBytes(matrixToSend[i]), 0, 8);
            }

            Core.Instance.MyData.SendData(ms.ToArray(), (int)ms.Length);

        }

        /// <summary>
        /// Start playing animation
        /// </summary>
        /// <param name="animationName"></param>
        public void StartAnimation(string animationName)
        {
            Logger.Instance.Log("Start animation - " + animationName);

            try
            {
                this.animationTimeElapsed = 0;
                this.previousTime = DateTime.Now;
                this.currentAnimationFrame = 0;
                this.currentAnimationSet = Core.Instance.SceneData.Animations[animationName];



                // Set initial positions
                foreach (AnimationNode node in ((KeyFrame)this.currentAnimationSet[0]).Nodes)
                {
                    SceneNode sn = null;
                    // Find the corresponding scene node
                    foreach (SceneNode child in this.rootSceneNode.Children)
                    {
                        if (node.Target.CompareTo(child.Name) == 0)
                        {
                            sn = child;
                            break;
                        }
                    }

                    Matrix3D m = sn.TransformMatrix;

                    m.OffsetX = node.X;
                    m.OffsetY = node.Y;
                    m.OffsetZ = node.Z;

                    sn.TransformMatrix = m;
                }

                Timer t = new Timer(50); //30fps
                t.Elapsed += new ElapsedEventHandler(animationTimer_Elapsed);
                t.Start();

            }
            catch (Exception ex)
            {
                Logger.Instance.Log("Failed to start animation. Reason: " + ex.Message);
            }

        }

        /// <summary>
        /// Animation timer elapsed event handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void animationTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            try
            {
                double elapsed = (DateTime.Now - this.previousTime).TotalSeconds;

                // Next key frame
                KeyFrame nextKF = (KeyFrame)this.currentAnimationSet[this.currentAnimationFrame + 1];
                // Current key frame
                KeyFrame curKF = (KeyFrame)this.currentAnimationSet[this.currentAnimationFrame];

                bool isFinal = false;
                this.animationTimeElapsed += elapsed;
                this.previousTime = DateTime.Now;

                if (this.animationTimeElapsed >= nextKF.TSec)
                {
                    // Switch to next key frame
                    this.currentAnimationFrame++;

                    // End of animation
                    if (this.currentAnimationFrame >= (this.currentAnimationSet.Count - 1))
                    {
                        isFinal = true;
                        (sender as Timer).Stop();
                        Logger.Instance.Log("Animation stopped.");
                    }
                }

                double x = 0;
                double y = 0;
                double z = 0;
                SceneNode sn = null;
                // Current Time / Key_Frame_Timespan 
                double d = (this.animationTimeElapsed - curKF.TSec) / (nextKF.TSec - curKF.TSec);
                int i = 0;
                for (i = 0; i < curKF.Nodes.Count; i++)
                {
                    AnimationNode nextNode = (AnimationNode)nextKF.Nodes[i];
                    AnimationNode curNode = (AnimationNode)curKF.Nodes[i];

                    // Find the corresponding scene node
                    foreach (SceneNode child in this.rootSceneNode.Children)
                    {
                        if (curNode.Target.CompareTo(child.Name) == 0)
                        {
                            sn = child;
                            break;
                        }
                    }

                    // Start calculate the position
                    Matrix3D m = sn.TransformMatrix;
                    if (!isFinal)
                    {

                        x = (nextNode.X - curNode.X) * d + curNode.X;
                        y = (nextNode.Y - curNode.Y) * d + curNode.Y;
                        z = (nextNode.Z - curNode.Z) * d + curNode.Z;

                        m.OffsetX = x;
                        m.OffsetY = y;
                        m.OffsetZ = z;
                    }
                    else
                    {
                        m.OffsetX = nextNode.X;
                        m.OffsetY = nextNode.Y;
                        m.OffsetZ = nextNode.Z;
                    }
                    sn.TransformMatrix = m;
                }
            }
            catch (Exception ex)
            {
                (sender as Timer).Stop();
                Logger.Instance.Log("Animation stopped.");
            }


        }

        #endregion

        #region IPresenter Implementations

        #region Functions

        public void OnCameraUpdate(byte[] buffer)
        {
            // Render captured video image
            LayoutRoot.Dispatcher.Invoke((Action)(delegate
            {
                CamVideoInfo info = Core.Instance.GetCamVideoInfo();
                PixelFormat pixelFormat;
                if (info.VideoPixelFormat == System.Drawing.Imaging.PixelFormat.Format24bppRgb)
                    pixelFormat = PixelFormats.Bgr24;
                else
                    pixelFormat = PixelFormats.Bgr32;

                Graphics display = Graphics.FromHwnd(IntPtr.Zero);

                BitmapSource bitmapSource = BitmapSource.Create
                    (info.Width, info.Height, display.DpiX, display.DpiY,
                    pixelFormat, null, buffer, info.Stride);
                VideoBrush.ImageSource = bitmapSource;
            }));
        }

        public void OnARUpdate(ArrayList results)
        {
            MainViewPort.Dispatcher.Invoke((Action)(delegate
            {
                // Reset the action marker detection state
                foreach (ActionMarker am in actions.Values)
                {
                    if (am.IsUsing)
                        am.IsDetected = false;
                }

                ARMarker markerData;
                SceneNode sNode = null;

                for (int i = 0; i < results.Count; i++)
                {
                    markerData = (ARMarker)results[i];
                    MatrixTransform3D markerPoseTransform3D = new MatrixTransform3D();
                    Matrix3D transformMatrix = MatrixUtil.GetWPFMatrix(markerData.TransMatrix);


                    if (markerData.ID == -1) // Board marker
                    {
                        if(results.Count == 1)
                            sNode = this.rootSceneNode;
                    }
                    else
                    {
                        // Action markers
                        if (actions.ContainsKey(markerData.ID))
                        {
                            ActionMarker am = actions[markerData.ID];

                            // Currently the system accepts locker action only
                            if ((am.SNode as ActSceneNode).ActionCode != 1) continue;

                            // Ignore this marker?
                            if (am.Ignore) continue;


                            sNode = am.SNode;

                            if (!am.IsUsing && !am.IsDetected)  // Totally new action marker
                            {
                                am.startCountDown();
                            }
                            else
                            {
                                if (am.IsUsing)
                                {
                                    // Update other users on this node's matrix
                                    SyncrhonizeNodeMatrix(am.LockedNode);
                                }
                            }
                            am.IsDetected = true;
                        }

                    }

                    if (sNode != null)
                    {
                        // Update matrix
                        transformMatrix = Matrix3D.Multiply(sNode.TransformMatrix, transformMatrix);
                        markerPoseTransform3D.Matrix = transformMatrix;
                        sNode.Model3D.Transform = markerPoseTransform3D;
                        UpdateChildMatrix(sNode, transformMatrix);

                        // Reset sNode
                        sNode = null;

                    }
                }

                // Check for released action
                foreach (ActionMarker am in actions.Values)
                {
                    if (am.IsDetected == false)
                    {
                        am.Ignore = false;
                        if(am.IsUsing)
                            ActionMarkerReleased(am);
                    }
                }

            }));
        }

        public void AlignCamera(double[] matrix)
        {
            MainCamera.ProjectionMatrix = MatrixUtil.GetWPFMatrix(matrix);
            MainCamera.ViewMatrix = Matrix3D.Identity;
        }

        public SceneNode GetCollision(double[] matrix)
        {
            
            SceneNode sn = null;
            try
            {
                Matrix3D matOrigin = Util.MatrixUtil.GetWPFMatrix(matrix);

                // Origin position
                Point3D originPos = new Point3D(matOrigin.OffsetX, matOrigin.OffsetY, matOrigin.OffsetZ);

                // Do collision testing on each scene nodes
                // nearest distance between each detected node with the locker
                double nearestDistance = 10000000;
                foreach (SceneNode node in this.rootSceneNode.Children)
                {
                    Matrix3D nodeMat = node.Model3D.Transform.Value;

                    Point3D nodePos = new Point3D(nodeMat.OffsetX, nodeMat.OffsetY, nodeMat.OffsetZ);
                    double nodeDistance = Point3D.Subtract(nodePos, originPos).Length;

                    Point3D testPoint = new Point3D(0, 0, nodeDistance);
                    testPoint = Point3D.Multiply(testPoint, matOrigin);

                    // Distance between testPoint to the scenenode
                    double distance = Point3D.Subtract(testPoint, nodePos).Length;
                    if (distance < nearestDistance)
                    {
                        nearestDistance = distance;
                        sn = node;
                    }
                }
                // Filter nearest node
                // Get bound distance
                double boundDistance = 10;
                SceneNode dummy = sn;
                do
                {
                    boundDistance += dummy.Scale.X;
                    dummy = dummy.ParentNode;
                } while (dummy != null);

                if (nearestDistance > boundDistance)
                    sn = null;
            }
            catch (Exception ex)
            {
                
            }

            return sn;
        }

        public void DisplayMsg(string message)
        {
            ScreenMessageTextBlock.Text = message;
        }

        #endregion
        #endregion
    }
}